<?php

namespace app\install\controller;

use app\install\util\Random;
use PDO;
use think\App;
use think\Cache;
use think\Config;
use think\Controller;
use think\Db;
use think\Env;
use think\Exception;
use think\exception\HttpResponseException;
use think\Request;
use think\Response;
use think\Url;
use think\View as ViewTemplate;

class Index extends Controller
{

    /**
     * Pdo原生实例对象
     *
     */
    protected $pdo;

    /**
     * 当前数据文件表前缀
     *
     * @var array
     */
    protected $table_prefix = ['cmy_'];

    /**
     * 每次执行语句条数
     *
     * @var int
     */
    protected $write_num = 20;

    protected $_agreement;

    protected $_outData;

    protected $sql_num;

    protected $admin_path_name;

    /**
     * 是否需要全局返回json输出
     * @var array
     */
    protected $jsonOut = true;

    /**
     * 系统初始化
     */
    public function _initialize()
    {
        //继承初始化
        parent::_initialize();

        //初始化相关数据
        @set_time_limit(1000);

        $file = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'version.php';
        if (!file_exists($file)) {
            exit(json_encode(['code' => 500, 'message' => '程序版本文件[application/version.php]不能为空']));
        }

        // 加载程序信息
        $sysConfig = include $file;

        //软件许可协议
        $this->_agreement = [
            //公司名称
            'company'             => '成都沉梦科技有限公司',
            //公司简称
            'companyabbreviation' => '沉梦科技',
            //软件名称
            'softname'            => $sysConfig['sys_appname'] ?? '沉梦云',
            //软件简称
            'softabbreviation'    => $sysConfig['sys_apptitle'] ?? '沉梦云',
            //公司官网
            'website'             => 'www.cmyo.cn',
            //服务器所在地
            'serverLocation'      => '四川成都',
            //版权时效
            'copyrightTime'       => '2022-2099',
        ];

        //页面返回的处理数据
        $this->_outData = [
            'name'         => $sysConfig['sys_appname'] ?? '沉梦云商城Pro',
            'type'         => $sysConfig['sys_type'] ?? '单商户',
            'version'      => $sysConfig['sys_version'] ?? '1.0.0',
            'version_name' => $sysConfig['sys_version_name'] ?? 'V1',
            'title'        => '安装引导',
            'website'      => $sysConfig['sys_website'] ?? 'pro.cmyo.cn',
            'github'       => $sysConfig['github'] ?? '',
            'gitee'        => $sysConfig['gitee'] ?? '',
            'copyright'    => $sysConfig['sys_author'] ?? '沉梦科技',
            'logo'         => '',
        ];

        // 初始化安装配置
        $config = config('install.config');
        foreach ($config as $key => $value) {
            $this->{$key} = $value;
        }

        if (is_file(ROOT_PATH . 'app/common/install.lock') && $this->request->action() != 'index') {
            $data = ['code' => 403, 'message' => '您已安装过, 如需重新安装, 请删除“网站安装目录/app/common/”下的install.lock文件<br/>如已删除，可刷新本页面后再试!'];
            $this->outJson($data);
        }
    }

    /**
     * 首页
     *
     */
    public function index()
    {
        return $this->success('欢迎使用', '', [
            'agreement' => $this->_agreement,
            'copyright' => $this->_outData,
        ]);
    }

    /**
     * 检测可用函数
     *
     * @param string  $name 函数名称
     * @param boolean $r    是否返回布尔值
     * @param boolean $m    是否必需
     * @return string
     */
    private function func_check($name = '', $r = false, $m = false)
    {
        if (function_exists($name)) {
            if ($r) {
                return true;
            }
            return '<i class="check check-success"></i>支持';
        } else {
            if ($r) {
                return false;
            }
            if ($m == false) {
                return '<i class="check"></i>不支持';
            } else {
                return '<i class="check check-danger"></i>不支持';
            }
        }
    }

    /**
     * 检测文件夹读写权限
     *
     * @param string  $path 读写路径
     * @param boolean $r    是否返回布尔值
     * @return string|boolean
     */
    private function dir_check($path = '', $r = false)
    {
        $path = strpos($path, ROOT_PATH) === false ? ROOT_PATH . $path : $path;
        if (is_dir($path)) {
            if (file_put_contents($path . 'write.html', '检测目录写入权限')) {
                if ($r) {
                    return true;
                }
                return '<i class="check check-success"></i>正常';
            }
        }
        if ($r) {
            return false;
        }
        return '<i class="check check-danger"></i>异常';
    }

    /**
     * 检测环境配置
     *
     * @param boolean $r    是否返回布尔值
     * @return string|boolean
     */
    private function env_check($r = false)
    {
        $path = ROOT_PATH . '.env';
        if (is_file($path)) {
            return $r ? true : '<i class="check check-success"></i>支持';
        }
        return $r ? false : '<i class="check check-danger"></i>不支持';
    }

    /**
     * 检测可用类
     *
     * @param string  $name 函数名称
     * @param boolean $r    是否返回布尔值
     * @param boolean $m    是否必需
     * @return string
     */
    private function class_check($name = '\PDO', $r = false, $m = false)
    {
        if (class_exists($name)) {
            if ($r) {
                return true;
            }
            return '<i class="check check-success"></i>已启用';
        } else {
            if ($r) {
                return false;
            }
            if ($m == false) {
                return '<i class="check"></i>未启用';
            } else {
                return '<i class="check check-danger"></i>未启用';
            }
        }
    }

    /**
     * 检测扩展
     *
     * @param string  $name 扩展名称
     * @param boolean $r    是否返回布尔值
     * @param boolean $m    是否必需
     * @return string
     */
    protected function extension_loaded($name = 'redis', $r = false, $m = false)
    {
        if (extension_loaded($name)) {
            if ($r) {
                return true;
            }
            return '<i class="check check-success"></i>已安装';
        } else {
            if ($r) {
                return false;
            }
            if ($m == false) {
                return '<i class="check"></i>未安装';
            } else {
                return '<i class="check check-danger"></i>未安装';
            }
        }
    }

    /**
     * 获取表单
     *
     * @return void
     */
    public function getFormList()
    {
        $form = config('install.form');
        return $this->success('成功', '', ['rows' => is_array($form) ? $form : []]);
    }

    /**
     * 获取检测项目
     *
     */
    public function getCheckList()
    {
        if (function_exists('saeAutoLoader') || isset($_SERVER['HTTP_BAE_ENV_APPID'])) {
            return $this->error('当前环境不支持本系统，请使用独立服务或云主机', null, [
                'error' => 1,
            ]);
        }

        $gd = $this->func_check('gd_info') ? gd_info()['GD Version'] : ['GD Version' => '0'];
        preg_match('/[\d]+\.[\d]+\.[\d]+/', $gd, $match1);
        $gd_version = is_array($match1) && isset($match1[0]) ? $match1[0] : '0';
        $data[0]    = [
            'name'  => '环境检查',
            'items' => [
                [
                    'name'    => '操作系统',
                    'tips'    => '-',
                    'value'   => PHP_OS,
                    'checked' => true,
                    'require' => '无限制',
                ],
                [
                    'name'    => '服务器程序',
                    'tips'    => '-',
                    'value'   => $_SERVER["SERVER_SOFTWARE"],
                    'checked' => true,
                    'require' => '无限制',
                ],
                [
                    'name'    => 'PHP版本',
                    'tips'    => '程序运行必需',
                    'value'   => PHP_VERSION,
                    'checked' => version_compare(PHP_VERSION, '7.0.0', '>'),
                    'require' => '>= 7.0.0',
                ],
                [
                    'name'    => 'GD图片库',
                    'tips'    => '图片处理必需',
                    'value'   => $gd,
                    'checked' => version_compare($gd_version, '1.0.0', '>'),
                    'require' => '必需开启',
                ],
                [
                    'name'    => 'PDO-Mysql',
                    'tips'    => '数据库必备',
                    'value'   => $this->class_check('\PDO', false, true),
                    'checked' => $this->class_check('\PDO', true),
                    'require' => '必需',
                ],
                [
                    'name'    => 'curl_exec',
                    'tips'    => '抓取网页',
                    'value'   => $this->func_check('curl_exec', false, true),
                    'checked' => $this->func_check('curl_exec', true),
                    'require' => '必需',
                ],
                [
                    'name'    => 'file_get_contents',
                    'tips'    => '读取文件',
                    'value'   => $this->func_check('file_get_contents', false, true),
                    'checked' => $this->func_check('file_get_contents', true),
                    'require' => '必需',
                ],
                [
                    'name'    => 'fileinfo',
                    'tips'    => $this->func_check('finfo_open', true) ? "保持现状" : "去php版本设置“安装扩展”里安装fileinfo",
                    'value'   => $this->func_check('finfo_open', false, true),
                    'checked' => $this->func_check('finfo_open', true),
                    'require' => '必需',
                ],
                [
                    'name'    => 'Memcache',
                    'tips'    => '内容缓存组件',
                    'value'   => $this->class_check('Memcache', false, true),
                    'checked' => $this->class_check('Memcache', true),
                    'require' => '必需',
                ],
                [
                    'name'    => 'openssl',
                    'tips'    => '支付安全必备',
                    'value'   => $this->func_check('openssl_encrypt', false, true),
                    'checked' => $this->func_check('openssl_encrypt', true),
                    'require' => '必需',
                ],
                // [
                //     'name'    => 'redis',
                //     'tips'    => $this->extension_loaded('redis', true) ? '保持现状' : '<a href="">查看安装教程<a>',
                //     'value'   => $this->extension_loaded('redis', false, true),
                //     'checked' => $this->extension_loaded('redis', true),
                //     'require' => '必需',
                // ],
            ],
        ];
        $data[1] = [
            'name'  => '读写权限',
            'items' => [
                [
                    'name'    => 'public',
                    'tips'    => $this->dir_check('public', true) ? '保持现状' : '请在网站程序目录新建public目录再试',
                    'value'   => $this->dir_check('public'),
                    'checked' => $this->dir_check('public', true),
                    'require' => '可读可写',
                ],
                // [
                //     'name'    => 'public/uploads',
                //     'tips'    => $this->dir_check('public/assets/uploads', true) ? '保持现状' : '请先在网站安装目录新建public/assets/uploads目录再试',
                //     'value'   => $this->dir_check('public/assets/uploads'),
                //     'checked' => $this->dir_check('public/assets/uploads', true),
                //     'require' => '可读可写',
                // ],
                [
                    'name'    => 'runtime',
                    'tips'    => $this->dir_check('runtime', true) ? '保持现状' : '请在网站程序目录新建runtime目录',
                    'value'   => $this->dir_check('runtime'),
                    'checked' => $this->dir_check('runtime', true),
                    'require' => '可读可写',
                ],
                // [
                //     'name'    => '.env',
                //     'tips'    => $this->env_check(true) ? '保持现状' : '请在网站安装目录新建.env文件并设置644权限',
                //     'value'   => $this->env_check(),
                //     'checked' => $this->env_check(true),
                //     'require' => '可读可写',
                // ],
            ],
        ];
        return $this->success('成功', null, ['rows' => $data]);
    }

    //页面定位测试
    public function hello($name = 'word')
    {
        return 'install hello,' . $name;
    }

    /**
     * 检查是否已经安装
     */
    public function checkInstall()
    {
        return $this->success('succ', '', [
            'checked' => is_file(ROOT_PATH . 'app/common/install.lock'),
        ]);

    }

    /**
     * 链接Pdo数据库并获取对象
     *
     * @param array $dbOptions 数据库配置信息
     * @return void
     */
    public function getPdoDb(array $dbOptions = [])
    {

        $dbhost = $dbOptions['dbhost'] ?? '127.0.0.1';
        $dbname = $dbOptions['dbname'] ?? 'root';
        $dbuser = $dbOptions['dbuser'] ?? 'root';
        $dbpwd  = $dbOptions['dbpwd'] ?? '123456';
        $dbport = $dbOptions['dbport'] ?? '3306';

        // 数据库类型
        $type   = Config::get('database.type', 'mysql');
        $pdo    = null;
        $option = $type . ":host=" . $dbhost;
        if ($dbport) {
            $option .= ";port=" . $dbport;
        }
        if ($dbname) {
            $option .= ";dbname={$dbname}";
        }
        // PDO配置
        $pdoAttr = [
            //设置数据库字段保持不变
            PDO::ATTR_CASE                     => PDO::CASE_NATURAL,
            //关闭pdo模拟功能
            PDO::ATTR_EMULATE_PREPARES         => false,
            //开启缓存查询
            PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
        ];
        $this->pdo = new PDO($option, $dbuser, $dbpwd, $pdoAttr);
        $this->pdo->exec("set names 'utf8mb4'");
        return $pdo;
    }

    /**
     * 处理生成进度语句
     *
     * @param array $data
     * @param boolean $flag
     * @return string
     */
    private function resMsg($data = [], $succ = true)
    {
        if ($succ) {
            $cs   = 'i-success';
            $csfa = 'i-icon';
        } else {
            $cs   = 'i-error';
            $csfa = 'i-icon';
        }
        $title   = $data['title'] ?? '';
        $content = $data['content'] ?? '';
        return '<li class="' . $cs . '"><div class="left"><span class="i-step">#' . ($data['sql_step'] + 1) . ' </span> <span class="i-title">' . $title . ': </span> <span class="i-content">' . $content . '</span></div><div class="right"><i class="' . $csfa . '" aria-hidden="true"></i> <i class="i-time">' . date('Y-m-d H:i:s') . '</i></div></li>';

    }

    /**
     * 简单的验证内容是否指定常见数值类型
     * @param  string $value  验证内容
     * @param  string $method 验证类型 mobile 手机号|money 金额|email 邮箱|username 用户名|password 密码
     * @return [type]         [description]
     */
    public function validateData($value = '', string $method = 'money')
    {
        switch ($method) {
            case 'email':
                return preg_match('/^[\w\-\.]+@[\w\-\.]+\.[\w]{2,6}$/', $value) == 1;
                break;
            case 'mobile':
                return preg_match('/^1[0-9]{10}$/', $value) == 1;
                break;
            case 'money':
                return preg_match('/^[0-9\.]+$/', $value) == 1;
                break;
            //用户名 支持邮箱用户名
            case 'username':
                return preg_match('/^[\w\-\.@]{5,20}$/', $value) == 1;
                break;
            //密码 支持常见特殊符号
            case 'password':
                return preg_match('/^[\w\-\.@!#%\$\^\*]{5,32}$/', $value) == 1;
                break;
            //过滤高发shell
            default:
                $preg = '/exec|shell_exec|ssh2_shell|preg_|system|chr\(|call_user_|select\s|update\s/i';
                return preg_match($preg, $value) != 1;
                break;
        }
    }

    /**
     * 保存数据库配置
     */
    private function saveDatabase($dbOptions = [])
    {
        $dbhost   = $dbOptions['dbhost'] ?? '127.0.0.1';
        $dbname   = $dbOptions['dbname'] ?? 'root';
        $dbuser   = $dbOptions['dbuser'] ?? 'root';
        $dbpwd    = $dbOptions['dbpwd'] ?? '123456';
        $dbport   = $dbOptions['dbport'] ?? '3306';
        $dbprefix = $dbOptions['dbprefix'] ?? '3306';
        $this->setEnv('database.hostname', $dbhost);
        $this->setEnv('database.database', $dbname);
        $this->setEnv('database.username', $dbuser);
        $this->setEnv('database.password', $dbpwd);
        $this->setEnv('database.hostport', $dbport);
        $this->setEnv('database.prefix', $dbprefix);
        $this->setEnv('APP_DEBUG', 'false');
        Cache::set('install.db', $dbOptions, 86400);
        return true;
    }

    /**
     * 缓存管理员和站点配置
     */
    private function saveWebsiteConfig($website = [])
    {
        Cache::set('install.data', $website, 86400);
    }

    /**
     *  提交配置数据
     *
     */
    public function onCheckConfig()
    {
        $dbOptions = input('post.database');

        // 链接数据库
        try {
            $this->getPdoDb($dbOptions);
        } catch (\Exception $th) {
            // 捕获DbException 此处注意DbException只能用Exception捕获
            $code = $th->getCode();
            if ($code == 2002) {
                return $this->error('数据库链接错误, 数据库地址填写错误');
            } elseif ($code == 1045) {
                return $this->error('数据库链接错误, 账号或密码错误');
            } elseif ($code == 1049) {
                return $this->error('数据库链接错误, 数据库名称对应的数据库不存在');
            }
            return $this->error('数据库链接错误，' . $th->getMessage());
        }

        // 保存数据库配置
        $this->saveDatabase($dbOptions);

        // 验证是否保存成功
        // if (!config('datebase.username', null) && !Env::get('datebase.username')) {
        //     return $this->error('数据库配置信息保存失败！');
        // }

        $website = input('post.website');
        if (!array_get('password', $website)) {
            return $this->error('管理员密码不能为空！');
        } elseif ($website['password'] != $website['password']) {
            return $this->error('管理员密码不一致！');
        } elseif (!array_get('username', $website)) {
            return $this->error('管理员账号不能为空！');
        } elseif (!$this->validateData(array_get('username', $website), 'username')) {
            return $this->error('管理员账号格式不正确！');
        } elseif (!array_get('email', $website)) {
            return $this->error('站长邮箱不能为空！');
        } elseif (!$this->validateData(array_get('email', $website), 'email')) {
            return $this->error('站长邮箱格式不正确！');
        }

        try {
            // 保存管理员和站点配置
            $this->saveWebsiteConfig($website);
        } catch (Exception $th) {
            return $this->error($th->getMessage());
        }

        return $this->success('保存成功', null, [
            'result' => ['sql_step' => -1],
        ]);
    }

    /**
     * 动态配置.env环境变量
     *
     * @param string $key 变量名 支持组
     * @param string $value 变量值
     * @return void
     */
    public function setEnv(string $key = '', string $value = '')
    {
        $file = ROOT_PATH . '.env';
        $data = '';
        if (is_file($file)) {
            $data = trim(file_get_contents($file));
        }

        if ($data == '') {
            // 没有文件就直接创建
            $data = <<<Env
           APP_DEBUG = false

           [APP]
           DEFAULT_TIMEZONE = Asia/Shanghai

           [DATABASE]
           TYPE = mysql
           HOSTNAME =
           HOSTPORT = 3306
           USERNAME =
           PASSWORD =
           DATABASE =
           PREFIX = cmypro_
           CHARSET = utf8
           DEBUG = true

           [LANG]
           default_lang = zh-cn

           [REDIS]
           REDIS_HOSTNAME =
           PORT = 6379
           REDIS_PASSWORD =
           SELECT = 0

           [QUEUE]
           QUEUE_NAME =
Env;
        }

        // 修改配置
        $key = strtoupper($key);
        if (strpos($key, '.') !== false) {
            $arr   = explode('.', $key, 2);
            $key1  = $arr[0];
            $key2  = $arr[1];
            $start = mb_strpos($data, '[' . $key1 . ']');
            if ($start !== false) {
                // 找到配置组
                $str_up    = mb_substr($data, 0, $start);
                $str_lower = mb_substr($data, $start);
                if (preg_match('/' . $key2 . ' =/', $str_lower)) {
                    // 找到就直接替换
                    $str_lower = preg_replace('/(\n' . $key2 . ' =(.*)|\n' . $key2 . ' =|\n' . $key2 . ' =([\s]{1,}))/', "\n" . $key2 . ' = ' . $value, $str_lower);
                    $data      = $str_up . $str_lower;
                } else {
                    // 未找到直接在组下面追加
                    $str_q     = '/\[' . $key1 . '\]/';
                    $str_h     = "[" . $key1 . "]\n" . $key2 . " = " . $value;
                    $str_lower = preg_replace($str_q, $str_h, $str_lower);
                    $data      = $str_up . $str_lower;
                }
            } else {
                // 未找到配置组 追加
                $data = $data . "\n[" . $key1 . "]\n" . $key2 . " = " . $value;
            }
        } else {
            // 说明是顶层配置
            if (preg_match('/' . $key . ' =/', $data)) {
                // 直接替换数据
                $data = preg_replace('/([\r\n]{0,1}' . $key . ' = (.*)|[\n]{0,1}' . $key . ' =|\n' . $key . ' =([\s]{1,}))/', "\n" . $key . ' = ' . $value, $data);
            } else {
                // 追加
                $data = $key . " = " . $value . "\n" . $data;
            }
        }

        file_put_contents($file, $data);
    }

    /**
     *  执行安装
     */
    public function onInstall()
    {

        $dbOptions = Cache::get('install.db');
        if (!is_array($dbOptions)) {
            $dbOptions = input('post.database');
        }

        $sql_step = input('post.sql_step/d', 0);

        // 链接数据库
        try {
            $this->getPdoDb($dbOptions);
        } catch (\Exception $th) {
            // 捕获DbException 此处注意DbException只能用Exception捕获
            $code = $th->getCode();
            if ($code == 2002) {
                return $this->error('数据库链接错误, 数据库地址填写错误');
            } elseif ($code == 1045) {
                return $this->error('数据库链接错误, 账号或密码错误');
            } elseif ($code == 1049) {
                return $this->error('数据库链接错误, 数据库名称对应的数据库不存在');
            }
            return $this->error('数据库链接错误，' . $th->getMessage(), null, [
                'list'   => [],
                'log'    => [],
                'ok'     => 0,
                'result' => ['sql_step' => $sql_step++],
            ]);
        }

        // 执行数据库写入
        $sqlFile = dirname(__DIR__) . '/data/install.sql';
        if (!is_file($sqlFile)) {
            return $this->error('数据库安装文件[app/' . $this->request->module() . '/data/install.sql]不存在，请检查程序完整性', '', ['ok' => 0, 'list' => [], 'log' => [], 'sqlFile' => str_replace(ROOT_PATH, '', $sqlFile)]);
        }

        $list = $log = [];
        for ($i = 0; $i < $this->write_num; $i++) {
            $sql = $this->getInstallSql(++$sql_step, $dbOptions['dbprefix'] ?? 'cmypro_');
            $res = $this->doInstallSql($sql_step, $sql);
            // try {
            //     $res = $this->doInstallSql($sql_step, $sql);
            // } catch (Exception $th) {
            //     $res = [
            //         'sql'      => $sql,
            //         'success'  => false,
            //         'content'  => $th->getMessage(),
            //         'title'    => '执行错误',
            //         'sql_num'  => $this->sql_num - 1,
            //         'sql_step' => $sql_step,
            //     ];
            // }
            if ($res['empty'] != 1) {
                $list[] = $res;
                $log[]  = $this->resMsg($res, $res['success'] === true);
            }
        }

        if ($sql_step >= $this->sql_num) {
            // 执行到最后一步，更新管理员和配置信息等
            $data   = Cache::get('install.data');
            $res    = $this->updateAdmin($data);
            $list[] = $res;
            $log[]  = $this->resMsg($res, $res['success'] === true);

            if (config('install.write.site')) {
                $res    = $this->updateWebsite($data);
                $list[] = $res;
                $log[]  = $this->resMsg($res, $res['success'] === true);
                $this->updateAdminPath();
            }

            if (config('install.write.version')) {
                $res    = $this->insertVersionInfo();
                $list[] = $res;
                $log[]  = $this->resMsg($res, $res['success'] === true);
            }
            return $this->success('数据写入成功', null, ['ok' => 1, 'list' => $list, 'log' => $log]);
        }
        return $this->success('当前执行完毕', '', ['list' => $list, 'log' => $log, 'ok' => 0, 'result' => $res]);
    }

    /**
     * 获取安装信息
     *
     */
    public function getInfo()
    {
        $data = Cache::get('install.data');
        if (is_array($data)) {
            @file_put_contents(ROOT_PATH . 'app/common/install.lock', '安装保护文件, 不要删, 否则后果自负!');
            return $this->success('安装成功', null, ['items' => $this->getInstallItems($data)]);
        }
        return $this->error('安装失败或异常, 管理员数据不存在');
    }

    /**
     * 获取安装信息列表
     *
     * @return array
     */
    private function getInstallItems($data = [])
    {
        return [
            [
                'label' => '管理员后台账号',
                'value' => $data['username'],
            ],
            [
                'label' => '管理员后台密码',
                'value' => $data['password'],
            ],
            [
                'label' => '管理员后台路径',
                'value' => rtrim($this->request->url(true), '/') . '/' . Cache::get('install.admin_path_name'),
            ],
            [
                'label' => '系统使用文档',
                'value' => config('install.config.doc_url'),
            ],
        ];
    }

    /**
     * 更新后台管理路径
     */
    private function updateAdminPath()
    {
        $file_path_old = ROOT_PATH . 'public' . DIRECTORY_SEPARATOR . 'admin.php';
        if (is_file($file_path_old)) {
            $this->admin_path_name = Random::alnum(14) . '.php';
            Cache::set('install.admin_path_name', $this->admin_path_name, 86400 * 3);
            @rename($file_path_old, ROOT_PATH . 'public' . DIRECTORY_SEPARATOR . $this->admin_path_name);
        }
    }

    /**
     * 执行写入SQL
     *
     * @param integer $sql_step
     * @param string $sql
     * @return array
     */
    public function doInstallSql($sql_step = 0, $sql = '')
    {

        if (trim($sql) == '') {
            return [
                'sql_step' => $sql_step,
                'sql'      => $sql,
                'empty'    => 1,
                'title'    => '无效操作',
                'content'  => 'SQL语句为空! ',
                'sql_num'  => $this->sql_num - 1,
                'success'  => false,
            ];
        }

        $tableName = '';
        if (preg_match('/TABLE IF NOT EXISTS `([\w]+)`/i', $sql, $match1)) {
            $tableName = $match1[1];
        }

        $tableTitle = '';
        if (preg_match("/CHARSET=utf8mb4 COMMENT='(.*?)'/i", $sql, $match2)) {
            $tableTitle = $match2[1];
        }

        if (!$tableName && !$tableTitle) {
            if (preg_match("/insert into `([\w]+)`/i", $sql, $match3)) {
                $tableName = $match3[1];
                $type      = 'insert';
                // 说明是insert语句
                $ret = [
                    'sql_step' => $sql_step,
                    'sql'      => $sql,
                    'title'    => '插入到表>>' . ($tableName ?: '未知'),
                    'content'  => '',
                    'sql_num'  => $this->sql_num - 1,
                    'success'  => false,
                ];
            } else {
                $type = 'insert';
                $ret  = [
                    'sql_step' => $sql_step,
                    'sql'      => $sql,
                    'title'    => '其他写入',
                    'content'  => '',
                    'sql_num'  => $this->sql_num - 1,
                    'success'  => false,
                ];
            }

        } else {
            $type = 'table';
            $ret  = [
                'sql_step' => $sql_step,
                'sql'      => $sql,
                'title'    => '创建表>>' . ($tableTitle ?: $tableName),
                'content'  => '',
                'sql_num'  => $this->sql_num - 1,
                'success'  => false,
            ];
        }
        try {
            $ret['success'] = true;
            $ret['content'] = $type == 'table' ? '创建成功' : '插入成功';
            $this->pdo->exec($sql);
        } catch (\Exception $th) {
            // 捕获DbException 此处注意DbException只能用\Exception捕获
            $ret['success'] = false;
            if ($type == 'table') {
                $ret['content'] = '创建失败，' . $th->getMessage();
            } else {
                $ret['content'] = '插入失败，' . $th->getMessage();
            }
        }
        $ret['empty'] = 0;
        return $ret;
    }

    /**
     * 获取当前执行语句
     *
     * @param integer $sql_step 当前步骤
     * @param string $prefix  数据表前缀
     * @return string
     */
    public function getInstallSql($sql_step = 0, $prefix = 'cmypro')
    {
        $sql           = file_get_contents(dirname(__DIR__) . '/data/install.sql');
        $sql_array     = explode(";\n", $sql);
        $this->sql_num = count($sql_array);
        if (isset($sql_array[$sql_step])) {
            return $this->sql_split($sql_array[$sql_step], $prefix);
        }
        return '';
    }

    /**
     * 获取密码密文
     *
     * @return string
     */
    public function getPwdhash($value)
    {
        $config = config('install');

        if (!array_has('encrypt.type', $config)) {
            throw new Exception("未配置加密驱动, 密码加密失败");
        }

        if ($config['encrypt']['type'] == 'function') {
            return $config['encrypt']['drive']($value);
        } else {
            $class  = $config['encrypt']['drive'] ?: null;
            $method = $config['encrypt']['method'] ?: 'encrypt';
            if ($class) {
                try {
                    return $class::$method($value);
                } catch (\Throwable $th) {
                    return (new $class())->$method($value);
                }
            }
            throw new Exception("未配置加密驱动, 密码加密失败");
        }
    }

    /**
     * 更新管理员
     */
    private function updateAdmin($data)
    {
        $data = !is_array($data) ? unserialize($data) : $data;
        if (!is_array($data)) {
            throw new Exception("管理员数据丢失, 修改失败");
        }

        $password = $data['password'] ?? '';
        if (!$password) {
            throw new Exception("管理员密码不能为空");
        }

        $config = config('install');

        $data = [
            'issuper'    => 1,
            'username'   => $data['username'],
            'email'      => $data['email'] ?? '',
            'qq'         => $data['qq'] ?? '',
            'password'   => pswCrypt($password),
            'updatetime' => time(),
        ];

        $res = [
            'sql_step' => $this->sql_num,
            'sql'      => '',
            'title'    => '更新管理员账号',
            'content'  => '',
            'sql_num'  => $this->sql_num - 1,
            'success'  => false,
        ];

        try {

            if (array_has('model.admin.model', $config)) {
                $model = array_get('model.admin.model', $config);
                if (!$model) {
                    $res['content'] = '未配置管理员表';
                    return $res;
                }

                $admin = $model::get(1);
                if ($admin) {
                    //更新
                    $admin->update($data, ['uid' => 1]);
                } else {
                    //添加
                    $data['createtime'] = time();
                    $data['nickname']   = 'Admin';
                    (new $model())->insert($data);
                }
            } else {
                $name  = array_get('model.admin.name', $config);
                $admin = Db::name($name)->find(1);
                if ($admin) {
                    //更新
                    $admin->update($data, ['uid' => 1]);
                } else {
                    //添加
                    $data['createtime'] = time();
                    $data['nickname']   = 'Admin';
                    Db::name($name)->insert($data);
                }
            }

            $res['content'] = '更新成功';
            $res['success'] = true;
        } catch (\Exception $th) {
            $res['content'] = '更新失败, ' . $th->getMessage();
        }
        return $res;
    }

    /**
     * 获取模型对象或Db查询对象
     *
     * @param string $modelname
     * @return \think\db\Query|think\Model
     */
    public function getModel($modelname = 'admin')
    {
        $config = config('install');
        if (array_has('model.' . $modelname . '.model', $config)) {
            $model = array_get('model.config.model', $config);
            if (class_exists($model)) {
                return new $model();
            }
            return null;
        } else {
            $name = array_get('model.' . $modelname . '.name', $config);
            if ($name) {
                return Db::name($name);
            }
            return null;
        }
    }

    /**
     * 更新网站配置
     */
    private function updateWebsite($website = [])
    {
        $website = !is_array($website) ? unserialize($website) : $website;
        if (!is_array($website)) {
            throw new Exception("网站配置数据丢失, 修改网站资料失败");
        }

        $res = [
            'sql_step' => $this->sql_num,
            'sql'      => '',
            'title'    => '更新网站配置',
            'content'  => '',
            'sql_num'  => $this->sql_num - 1,
            'success'  => false,
        ];

        try {
            $config     = config('install');
            $nameField  = array_get('model.config.nameField', $config);
            $valueField = array_get('model.config.valueField', $config);
            $model      = $this->getModel('config');
            if ($model && is_object($model)) {
                $row = $model->where($nameField, 'sitename')->find();
                if ($row) {
                    //更新
                    $row->update([
                        $valueField => $website['sitename'],
                    ], [$nameField => 'sitename']);
                } else {
                    //添加
                    $data['createtime'] = time();
                    $data['nickname']   = 'Admin';
                    (new $model())->insert($data);
                }
                $res['content'] = '更新成功';
                $res['success'] = true;
            } else {
                $res['content'] = '未设置网站配置表';
            }
        } catch (\Exception $th) {
            $res['content'] = '更新失败, ' . $th->getMessage();
        }
        return $res;
    }

    /**
     * 保存系统配置
     *
     * @param string $name
     * @param mixed  $value
     * @return void
     */
    private function saveSetting($name = '', $value)
    {
        if ($name) {
            $config     = config('install');
            $nameField  = array_get('model.config.nameField', $config);
            $valueField = array_get('model.config.valueField', $config);
            $model      = $this->getModel('config');
            if ($model && is_object($model)) {
                $query = $model->where($nameField, $name)->find();
                if ($query) {
                    $query->update([
                        $valueField => $value,
                    ]);
                } else {
                    $model->insert([
                        $nameField  => $name,
                        $valueField => $value,
                    ]);
                }
            }
        }
    }

    /**
     * 写入版本信息
     */
    private function insertVersionInfo()
    {
        // 更新系统版本信息
        $res = [
            'sql_step' => $this->sql_num,
            'sql'      => '',
            'title'    => '写入版本信息',
            'content'  => '',
            'sql_num'  => $this->sql_num - 1,
            'success'  => false,
        ];
        $sysConfigPath = dirname(dirname(__DIR__)) . '/version.php';
        if (file_exists($sysConfigPath)) {
            $sysConfig = include $sysConfigPath;
            try {
                $this->saveSetting('sys_build', $sysConfig['sys_build'] ?? 1026);
                $this->saveSetting('sys_version', $sysConfig['sys_version'] ?? '1.0.0.26');
                $this->saveSetting('sys_version_sql', $sysConfig['sys_version_sql'] ?? 1026);
                $this->saveSetting('sys_build_sql', $sysConfig['sys_build_sql'] ?? 1017);
                $res['content'] = '写入成功';
                $res['success'] = true;
            } catch (Exception $th) {
                $res['content'] = '写入失败, ' . $th->getMessage();
            } catch (\Throwable $th) {
                $res['content'] = '写入失败, ' . $th->getMessage();
            }
        } else {
            $res['content'] = '写入失败, 系统版本文件不存在';
        }
        return $res;
    }

    /**
     * SQL语句处理
     *
     * @param string $sql
     * @param string $prefix
     * @return string
     */
    private function sql_split($sql, $prefix): string
    {
        //去掉注释 /* */：/\*{1,2}[\s\S]*?\*/    /\*[\s\S]+?\*/   /(\/\*.*\*\/)|(#.*?\n)/s
        $sql = preg_replace('/(\/\*.*\*\/)/s', '', $sql);
        //删除空白行 ^\s*\n
        //$sql=preg_replace('/($\s*$)|(^\s*^)/m','\n',$sql);
        //删除 注释1 //：//[\s\S]*?\n
        // $sql = preg_replace('//[\s\S]*?\n', '', $sql);
        //删除 注释2 --
        $sql = preg_replace('/\-\-([\s]+?)(.*?)\n/', '', $sql);
        //替换 前缀
        $sql = str_replace($this->table_prefix, $prefix, $sql);

        //替换
        $sql = preg_replace("/TYPE=(InnoDB|MyISAM|MEMORY)( DEFAULT CHARSET=[^; ]+)?/", "ENGINE=\\1 DEFAULT CHARSET=utf8mb4", $sql);
        //替换换行符
        $sql = str_replace("\r", "\n", $sql);
        //删除 多余换行符 \n\n
        $sql = preg_replace(['/^[\n]+/', '/\n\n/', '/;$/'], ['', '', ''], $sql);
        return $sql;
        // $ret        = array();
        // $num        = 0;
        // $queriesArr = explode(";\n", trim($sql));
        // // unset($queriesArr['BEGIN']);
        // //unset($queriesArr['COMMIT']);
        // unset($sql);
        //return $queriesArr;
        // foreach ($queriesArr as $query) {
        //     $ret[$num] = '';
        //     $queries   = explode("\n", trim($query));
        //     $queries   = array_filter($queries);

        //     foreach ($queries as $query) {
        //         $str1 = substr($query, 0, 1);
        //         if ($str1 != '#' && $str1 != '-' && $query != 'BEGIN' && $query != 'COMMIT') {
        //             $ret[$num] .= $query;
        //         }
        //     }
        //     $num++;
        // }
        // return $ret;

    }

    //完成安装 删除安装执行文件
    public function complete()
    {

        if (is_file(ROOT_PATH . 'install/' . 'install.lock')) {
            $flag = @unlink(ROOT_PATH . 'install/' . 'install.lock');
        }

        // 删除安装数据
        Cache::rm('install.data');
        Cache::rm('install.db');
        Cache::rm('install.admin_path_name');

        if ($flag) {
            if (is_dir(dirname(__DIR__))) {
                deleteDir(dirname(__DIR__));
                if (is_dir(dirname(__DIR__))) {
                    $res['message'] = "安装文件删除失败！";
                    $res['code']    = 500;
                } else {
                    $res['message'] = "安装文件删除成功！";
                    $res['code']    = 200;
                }
            }
        } else {
            $res['message'] = "安装文件删除失败！";
            $res['code']    = 200;
        }
        return json($res);
    }

    /**
     * 强制输出Html
     * @param  string $msg   输出内容
     */
    public function outHtml(string $html)
    {
        $response = \think\Response::create($html, 'html')->header([]);
        throw new \think\Exception\HttpResponseException($response);
    }

    /**
     * 强制输出Json
     * @param  string $msg   输出内容
     */
    public function outJson(array $result)
    {
        if (class_exists('\think\Exception\HttpResponseException')) {
            $response = \think\Response::create(json_encode($result, JSON_UNESCAPED_UNICODE), 'json')->header([]);
            throw new \think\Exception\HttpResponseException($response);
        } else {
            @header("Content-Type:application/json; charset=UTF-8");
            if (is_array($result)) {
                $result['timestamp'] = time();
                exit(json_encode($result, JSON_UNESCAPED_UNICODE));
            }
            exit(json_encode(['code' => 500, 'message' => '系统错误，请稍后再试!', 'timestamp' => time()]));
        }
    }

    /**
     * 操作成功跳转的快捷方法
     * @access protected
     * @param mixed  $msg    提示信息
     * @param string $url    跳转的 URL 地址
     * @param mixed  $data   返回的数据
     * @param int    $wait   跳转等待时间
     * @param array  $header 发送的 Header 信息
     * @return void
     * @throws HttpResponseException
     */
    protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
    {
        if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) {
            $url = Request::instance()->server('HTTP_REFERER');
        } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) {
            $url = Url::build($url);
        }

        $type   = 'json';
        $result = [
            'code'    => 200,
            'message' => $msg,
            'msg'     => $msg,
            'data'    => $data,
            'url'     => $url,
            'wait'    => $wait,
        ];

        if ('html' == strtolower($type)) {
            $template = Config::get('template');
            $view     = Config::get('view_replace_str');

            $result = ViewTemplate::instance($template, $view)
                ->fetch(Config::get('dispatch_success_tmpl'), $result);
        }

        $response = Response::create($result, $type)->header($header);

        throw new HttpResponseException($response);
    }

    /**
     * 操作错误跳转的快捷方法
     * @access protected
     * @param mixed  $msg    提示信息
     * @param string $url    跳转的 URL 地址
     * @param mixed  $data   返回的数据
     * @param int    $wait   跳转等待时间
     * @param array  $header 发送的 Header 信息
     * @return void
     * @throws HttpResponseException
     */
    protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
    {
        if (is_null($url)) {
            $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);';
        } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) {
            $url = Url::build($url);
        }

        $type   = 'json';
        $result = [
            'code'    => 500,
            'msg'     => $msg,
            'message' => $msg,
            'data'    => $data,
            'url'     => $url,
            'wait'    => $wait,
        ];

        if ('html' == strtolower($type)) {
            $template = Config::get('template');
            $view     = Config::get('view_replace_str');

            $result = ViewTemplate::instance($template, $view)
                ->fetch(Config::get('dispatch_error_tmpl'), $result);
        }

        $response = Response::create($result, $type)->header($header);

        throw new HttpResponseException($response);
    }
}